import wx, os, random

from twisted.internet import wxreactor
wxreactor.install()

#apparently this installs the reactor automatically so needs to be after the wxreactor install
from twisted.cred import portal, checkers
from twisted.spread import pb
from twisted.internet import reactor

ID_ABOUT=101
ID_OPEN=102
ID_BUTTON1=110
ID_BUTTON2=111
ID_BUTTON3=112
ID_BUTTON4=113
ID_BUTTON5=114
ID_BUTTON6=115
ID_BUTTON7=116
ID_EXIT=200

class MainWindow(wx.Frame):
	def __init__(self,parent,id,title):
		#required for images i believe
		wx.InitAllImageHandlers()
		
		self.dirname=''
		wx.Frame.__init__(self,parent,wx.ID_ANY, title, style=wx.DEFAULT_FRAME_STYLE)

		''' A Statusbar in the bottom of the window '''
		self.CreateStatusBar()

		''' Setting up the menu. '''
		filemenu= wx.Menu()
		filemenu.Append(ID_ABOUT, "&About"," Information about this program")
		filemenu.AppendSeparator()
		filemenu.Append(ID_EXIT,"E&xit"," Terminate the program")

		''' Creating the menubar. '''
		menuBar = wx.MenuBar()
		menuBar.Append(filemenu,"&File") # Adding the "filemenu" to the MenuBar
		self.SetMenuBar(menuBar)  # Adding the MenuBar to the Frame content.
		wx.EVT_MENU(self, ID_ABOUT, self.OnAbout)
		wx.EVT_MENU(self, ID_EXIT, self.OnExit)
		wx.EVT_MENU(self, ID_OPEN, self.OnOpen)

		''' Add a text and a list control '''
		self.control = wx.TextCtrl(self, 1, size=(800,100), style=wx.TE_MULTILINE)
		#should be this type of control i think, don't know how to add to the fucker though
		#self.listCtrl = wx.ListCtrl(self, 2, size=(1,1), style=wx.LC_REPORT|wx.LC_VRULES|wx.LC_HRULES)	
		self.listCtrl = wx.ListCtrl(self, 2, size=(1,1), style=wx.LC_LIST)	
		
		''' Set up the buttons '''
		#create a sizer to contain the buttons
		self.sizer2 = wx.BoxSizer(wx.VERTICAL)
		self.buttons=[]

		self.buttons.append(wx.Button(self, ID_BUTTON1, "Show Connected Clients"))
		self.sizer2.Add(self.buttons[0],1,wx.EXPAND)
		
		self.buttons.append(wx.Button(self, ID_BUTTON2, "Close All Games"))
		self.sizer2.Add(self.buttons[1],1,wx.EXPAND)
		
		self.buttons.append(wx.Button(self, ID_BUTTON3, "Kick All Clients"))
		self.sizer2.Add(self.buttons[2],1,wx.EXPAND)
		
		self.buttons.append(wx.Button(self, ID_BUTTON4, "Show all games"))
		self.sizer2.Add(self.buttons[3],1,wx.EXPAND)
		
		self.buttons.append(wx.Button(self, ID_BUTTON5, "Create a Game"))
		self.sizer2.Add(self.buttons[4],1,wx.EXPAND)
		
		#hook the buttons up to their events
		wx.EVT_BUTTON(self, ID_BUTTON1, self.OnShowClients)
		wx.EVT_BUTTON(self, ID_BUTTON2, self.OnCloseAllGames)
		wx.EVT_BUTTON(self, ID_BUTTON3, self.OnKickClients)
		wx.EVT_BUTTON(self, ID_BUTTON4, self.OnShowGames)
		wx.EVT_BUTTON(self, ID_BUTTON5, self.OnCreateGame)
		
		# Use some sizers to see layout options
		self.sizer=wx.BoxSizer(wx.VERTICAL)

		#self.Refresh(True)
		#self.Update()		
		
		#add the textbox
		self.sizer.Add(self.control,1,wx.EXPAND)
		self.sizer.Add(self.listCtrl,2,wx.EXPAND)
		#add the button sizer (and all buttons contained in there)
		self.sizer.Add(self.sizer2,0,wx.EXPAND)
	
		#Layout sizers
		self.SetSizer(self.sizer)
		self.SetAutoLayout(1)
		self.sizer.Fit(self)
		
		#show the window
		self.Show(1)

	def OnShowGames(self,e):
		self.appText(realm.server.listGames())
		self.updateGames()
		
	def OnCreateGame(self,e):
		if(realm.server.createGame("Rand test game: " + str(random.random()), realm.server.user)):
			self.updateGames()

	def OnCloseAllGames(self,e):
		realm.server.killAllGames()
		self.updateGames()

	def OnAbout(self,e):
		#create a dialogue
		d= wx.MessageDialog(self, 'A test game server using Twisted and wxPython', 'About Proggy', wx.OK)
		#show it modal like
		d.ShowModal()
		#once we come back kill it
		d.Destroy()
	
	def OnShowClients(self,e):
		self.Refresh(True)
		self.Update()
		
	def OnKickClients(self,e):
		pass
	
	def OnExit(self,e):
		self.Close(True)  # Close the frame.
	
	def OnOpen(self,e):
		""" Open a file"""
		dlg = wx.FileDialog(self, "Choose a file", self.dirname, "", "*.*", wx.OPEN)
		if dlg.ShowModal() == wx.ID_OK:
			self.filename=dlg.GetFilename()
			self.dirname=dlg.GetDirectory()
			f=open(os.path.join(self.dirname, self.filename),'r')
			self.control.SetValue(f.read())
			f.close()
		dlg.Destroy()
		
	def setText(self, text):
		self.control.SetValue(text)

	def appText(self, text):
		self.control.WriteText(text+"\n")

	def updateGames(self):
		self.listCtrl.ClearAll()
		games = realm.server.getGames()
		i=0
		while i < len(games):
			self.listCtrl.Append([games[i]])
			i+=1


#stub function to allow the output to redirect easy.  This is a dumb way of doing it but it works so lay off alright.  You got a real attitude problem
def outText(textToWrite):
	frame.appText(textToWrite)
	
def updateGames():
	frame.updateGames()

""" TWISTED CODE """	

#serverwide variables
class GameServer:
	def __init__(self):
		self.games = {} # indexed by name
		self.user = User("Administrator")

	#create a new game
	def createGame(self, gameName, user):
		outText("Got request from " + str(user) + " to create " + gameName)
		if not self.games.has_key(gameName):
			self.games[gameName]=GameInstance(gameName)
			self.joinGame(gameName, user)
		else:
			outText("Failed")
			return None
		updateGames()
		
		return self.games[gameName]

	def destroyGame(self, gameName):
		if self.games.has_key(gameName):
			outText("Deleting game: " + gameName)
			del self.games[gameName]
		else:
			outText("No game to delete:" + gameName)
		updateGames()
	
	#assign an avatar to a game
	def joinGame(self, gameName, user):
		outText("Got request from " + str(user) + " to join " + gameName)
		if self.games.has_key(gameName):
			self.games[gameName].addUser(user)
		else:
			outText("Failed")
			return None
		return self.games[gameName]


	def leaveGame(self, gameName, user):
		outText("Got request from " + str(user) + " to leave " + gameName)
		if self.games.has_key(gameName):
			self.games[gameName].remUser(user)
			if len(self.games[gameName].users)==0:
				self.destroyGame(gameName)
		else:
			outText("Failed")
	
	#return the games as a big string
	def listGames(self):
		gameList = ""
		for game in self.games:
			if gameList!="":
				gameList += ", " #add a comma to the string if this isn't the start of the string
			gameList += (game)
		return gameList
	
	#this function scares me
	def killAllGames(self):
		outText("Not bloody likely")
	
	#return the games as an array of strings
	def getGames(self):
		return self.games.keys()

#grabs an avatar instance for the client
class GameRealm:
	__implements__ = portal.IRealm
	
	#i don't know if i can override and still get the origional on called?
	def __init__(self):
		self.avatars = {}

	#i need to get more info in here than just avatarID
	def requestAvatarOld(self, avatarID, mind, *interfaces):
		#raise an error if pb.IPerspective isn't in interfaces list i think
		assert pb.IPerspective in interfaces

		#grab an avatar instance for the client and pass it back.  set avatar.detached to callback on disconnect
		#use IP address and port to decide on avatar for disconnects?
		#ip, playername and rand no?
		
		avatar = User(avatarID)
		
		#set the avatar's server to an instance of the GameServer class
		avatar.server = self.server
		avatar.attached(mind)
		return pb.IPerspective, avatar, lambda a=avatar:a.detached(mind)

    # anonymous users get independent (but temporary) Avatars
    # named users get their own persistent one
	def requestAvatar(self, avatarID, mind, *interfaces):
		assert pb.IPerspective in interfaces
		if avatarID == checkers.ANONYMOUS:
			outText("Created anon avatar")
			return pb.IPerspective, MyAvatar(), lambda:None
		else:
			# why aren't the usernames bound to the avatars somehow?
			if not self.avatars.has_key(avatarID):
				outText("Created avatar: " + str(avatarID))
				avatar = User(avatarID)
				avatar.server = self.server
				self.avatars[avatarID] = avatar
			else:
				outText("Reattached avatar: " + str(avatarID))
				avatar = self.avatars[avatarID]
			
			#attach the client to the avatar instance
			avatar.attached(mind)
			
			return pb.IPerspective, avatar, lambda a=avatar:a.detached(mind)
	
"""
user interface object, the user can call and read any variables or funcs in their copy of this object.
Users should be able to stay in games even though they've lost their mind?
"""
class User(pb.Avatar):
	def __init__(self, name):
		self.name = name
		self.frame = 0
		self.game = None
		self.server = None

	def attached(self, mind):
		outText("Attached a mind: " + str(mind) + " to avatar:" + self.name)
		self.remote = mind

	def detached(self, mind):
		outText("Detached a mind: " + str(mind) + " from avatar:" + self.name)
		self.remote = None
		self.perspective_leaveGame()

	def perspective_listGames(self):
		return self.server.listGames()
	
	def perspective_getGames(self):
		return self.server.getGames()
		
	def perspective_joinGame(self, gameName):
		if self.game != None:
			self.perspective_leaveGame()

		self.game = self.server.joinGame(gameName, self)
		if self.game != None:
			return True
		else:
			return False
			
	#this could probably be called on the game directly but it doesn't really matter
	def perspective_leaveGame(self):
		if self.game != None:
			self.server.leaveGame(self.game.name, self)
			self.game = None
			return True
		else:
			return False

	def perspective_createGame(self, gameName):
		#is this insecure?
		self.game = self.server.createGame(gameName, self)
		if self.game!=None:
			return True
		else:
			return False
		
	#function sends shit back to the client
	def send(self, message):
		self.remote.callRemote("print", message)
		
	#should i make a stack of actions to process outta this young fella?  or just restrict to single action per frame.  I think a stack would probably be a good idea even if we make the stack have a max size of 1
	def perspective_mouseClick(self, pos, action):
		outText( "I am " + self.name + " perspective_mouseClick(" + str(pos) + ")")
		#self.game.clickAction(pos)
		#need to inform clients about clicks here
		#self.remote.callRemote("print", str(pos))
		
	def perspective_setFrame(self, frameNo):
		outText("I am" + self.name + "perspective_setFrame("+ str(frameNo) + ")")
		self.frameNo = frameNo
		

#individual game
class GameInstance(pb.Viewable):
	def __init__(self, gameName):
		self.name = gameName
		self.users = []
		self.startTime = 0
		self.frameNumber = 0
		self.gamePaused = 0
		self.gameStarted = 0
		self.usersReady = 0
	
	def sendCommand(self, fromUser):
		for user in self.users:
			user.command(fromUser.name, pos, action)
	
	def addUser(self, user):
		self.users.append(user)
		outText(self.name + " got new user, total: " + str(len(self.users)))

	def remUser(self, user):
		i=0
		while i<len(self.users):
			if self.users[i]==user:
				del self.users[i]
		outText(self.name + " lost a user, total: " + str(len(self.users)))

	#wtf is the difference between perspective_ and view_?
	def view_send(self, fromUser, message):
		for user in self.users:
			user.send("%s: %s" % (fromUser.name, message))

#todo GET RID OF THIS SHIT
realm = GameRealm()
realm.server = GameServer()

#i need to write a checker here that allows temp usernames without passwords or proper username/pass pairs
"""
class myChecker:
	interface.implements(ICredentialsChecker)
	credentialInterfaces = credentials.IPluggableAuthenticationModules,
    service = 'Twisted'
	def requestAvatarId(self, credentials):
		#wtf is this?
		if not pamauth:
			return defer.fail(UnauthorizedLogin())
		d = pamauth.pamAuthenticate(self.service, credentials.username, credentials.pamConversion)
		d.addCallback(lambda x: credentials.username)
		return d
		
checker = myChecker
"""
checker = checkers.InMemoryUsernamePasswordDatabaseDontUse()

checker.addUser("anon", "anon")
checker.addUser("bob", "secret")
checker.addUser("carol", "fido")
checker.addUser("user2", "pass2")

p = portal.Portal(realm, [checker])

reactor.listenTCP(8800, pb.PBServerFactory(p))

app = wx.PySimpleApp()
frame = MainWindow(None, -1, "Game server")
reactor.registerWxApp(app)
reactor.run()
